<template>
  <form class="cmp cmp-parse row">
    <div class="col-4">
      <fieldset>
        <legend>参数设置</legend>
        <div class="fieldset-content">
          <div class="form-group form-item-row" align="center">
            <div class="form-item-label">图片</div>
            <div class="form-item-input"><input class="form-control" type="text" v-model="imagePath" placeholder="请选择主图" readonly /></div>
            <div class="form-item-suffix"><x-button @click="setImagePath">选择...</x-button></div>
          </div>
          <div class="form-group form-item-row" align="center">
            <div class="form-item-label">隐藏类型</div>
            <div class="form-item-input">
              <select class="form-control" v-model="hideType">
                <option v-for="(v,i) in hideTypeList" :key="i" :value="v.value">{{v.label}}</option>
              </select>
            </div>
          </div>
          <div class="form-group form-item-row" align="center">
            <div class="form-item-label">阈值</div>
            <div class="form-item-input">
              <input class="form-control" type="number" :min="1" :step="1" :max="7" v-model.trim="threshold" placeholder="请输入阈值" />
            </div>
          </div>
          <template v-if="hideType==='image'">
            <div class="form-group form-item-row" align="center">
              <div class="form-item-label">宽度</div>
              <div class="form-item-input">
                <input class="form-control" type="number" :min="1" :step="1" v-model.trim="imgWidth" placeholder="请输入隐藏图片的宽度" />
              </div>
            </div>
            <div class="form-group form-item-row" align="center">
              <div class="form-item-label">高度</div>
              <div class="form-item-input">
                <input class="form-control" type="number" :min="1" :step="1" v-model.trim="imgHeight" placeholder="请输入隐藏图片的高度" />
              </div>
            </div>
            <div class="form-group form-item-row" align="center">
              <div class="form-item-label">导出位置</div>
              <div class="form-item-input"><input class="form-control" type="text" v-model="saveFilePath" placeholder="请选择文件处理后的存储位置" /></div>
              <div class="form-item-suffix"><x-button @click="setSaveFilePath">选择...</x-button></div>
            </div>
          </template>
          <div class="form-group form-group-main"></div>
          <div class="form-group form-item-row" align="center">
            <x-button type="primary" block @click="parse" :loading="loadingCount > 0">开始处理</x-button>
          </div>
        </div>
      </fieldset>
    </div>
    <div class="col-8">
      <fieldset>
        <legend>结果预览</legend>
        <div class="fieldset-content">
          <div class="form-group form-group-main">
            <img v-if="hideType==='image'" :src="resultImageUrl" />
            <textarea v-else-if="hideType==='text'" class="form-control" v-model="resultText" readonly></textarea>
          </div>
          <div v-if="hideType==='image'" class="form-group form-item-row" align="center">
            <x-button type="primary" block @click="exportFile">导出</x-button>
          </div>
        </div>
      </fieldset>
    </div>
  </form>
</template>

<script>
const fs = global.require('fs')
const path = global.require('path')
const {remote} = global.require('electron')
const {dialog} = remote
const selfWindow= remote.getCurrentWindow()

const getImageDataUrl = (imagePath) => {
  return new Promise((resolve, reject) => {
    let paramType = (typeof imagePath)
    let ext = (path.extname(imagePath) || '').substring(1) || 'png'
    if (!imagePath) { reject('图片地址不能为空') }
    else if (paramType === 'string') {
      fs.readFile(imagePath, {encoding: 'base64'}, (err, data) => {
        if (err) {reject('图片读取失败')}
        else if (!data) {reject('未找到有效图片数据')}
        else {
          resolve(`data:image/${ext};base64,${data}`)
        }
      })
    }
    else { reject('图片地址无效') }
  })
}
const num2binary = (num) => {
  if (isNaN(parseInt(num))) {
    return ''
  } else {
    return num.toString(2).padStart(8, '0')
  }
}
const createImageData = (width, height) => {
  const canvas = document.createElement('canvas')
  const ctx = canvas.getContext('2d')
  return ctx.createImageData(width, height)
}

export default {
  name: 'parse',
  data () {
    return {
      saveFilePath: this.$sharedObject('desktopPath'),
      imagePath: '',
      imageUrl: '',
      imgWidth: 1,
      imgHeight: 1,
      threshold: 2,
      resultCanvas: null,
      resultImageUrl: '',
      resultText: '',
      hideType: 'image',
      hideTypeList: [
        {value: 'image', label: '图片'},
        {value: 'text', label: '文本'},
      ],
      loadingCount: 0,
    }
  },
  methods: {
    // 选择图片
    setImagePath () {
      let filePaths = dialog.showOpenDialogSync(selfWindow, {
        title: '选择文件',
        buttonLabel:'确定',
        defaultPath: this.$sharedObject('desktopPath'),
        properties: ['openFile']
      })
      if (filePaths instanceof Array && filePaths.length > 0) {
        this.imagePath = filePaths[0]
        this.loadingCount++
        getImageDataUrl(this.imagePath).then((dataUrl) => {
          this.imageUrl = dataUrl
        }).catch((err) => {
          this.$toast(err)
        }).finally(() => {
          this.loadingCount--
        })
      }
    },
    // 设置解密文件保存位置
    setSaveFilePath () {
      let filePaths = dialog.showOpenDialogSync(selfWindow, {
        title: '选择目录',
        buttonLabel:'确定',
        defaultPath: this.$sharedObject('desktopPath'),
        properties: ['openDirectory']
      })
      if (filePaths instanceof Array && filePaths.length > 0) {
        this.saveFilePath = filePaths[0]
      }
    },
    // 获取imgData
    getImgData (imgObj) {
      let canvasObj = document.createElement('canvas')
      canvasObj.width = imgObj.width
      canvasObj.height = imgObj.height
      let ctx = canvasObj.getContext('2d')
      ctx.drawImage(imgObj, 0, 0, canvasObj.width, canvasObj.height)
      let imgData = ctx.getImageData(0, 0, canvasObj.width, canvasObj.height)
      return imgData.data
    },
    // 表单校验
    checkForm () {
      let result = true
      if (!this.imagePath) {
        result = false
        this.$toast('图片地址不能为空')
      }
      if (this.hideType === 'image') {
        if (!this.imgWidth || this.imgWidth <= 0) {
          result = false
          this.$toast('请输入正确的隐藏图片宽度')
        }
        if (!this.imgHeight || this.imgHeight <= 0) {
          result = false
          this.$toast('请输入正确的隐藏图片高度')
        }
      }
      if (!this.threshold || this.threshold <= 0 || this.threshold >= 8) {
        result = false
        this.$toast('阈值应为1~7的数字')
      }
      return result
    },
    // 解析
    parse () {
      if (!this.checkForm()) return
      this.loadingCount++
      this.$jstools.loadImage(this.imageUrl).then(imgObj => {
        if (!imgObj) {
          return this.$toast('图片加载失败')
        }
        let imgData = this.getImgData(imgObj)
        if (this.hideType === 'image') {
          this.parseHideImage(imgData, this.imgWidth, this.imgHeight, this.threshold)
        } else if (this.hideType === 'text') {
          this.parseHideText(imgData, this.threshold)
        }
      }).finally(() => {
        this.loadingCount--
      })
    },
    // 解析隐藏图片
    parseHideImage (imageData, width, height, threshold = 2) {
      this.loadingCount++
      const count = Math.floor(width * height * 32 / threshold)
      const resultImageData  = createImageData(width, height)
      let index = 0
      let tmpStr = ''
      for (let i = 0; i <= count - 4; i += 4) {
        const r = imageData[i]
        const g = imageData[i + 1]
        const b = imageData[i + 2]
        const a = imageData[i + 3]
        const binStr = [num2binary(r), num2binary(g), num2binary(b), num2binary(a)].map(str => {
          return str.slice(8 - threshold, 8)
        }).join('')
        tmpStr += binStr
        const numCount = Math.floor(tmpStr.length / 8)
        for (let i = 0; i < numCount; i++) {
          const binary = tmpStr.slice(0, 8)
          tmpStr = tmpStr.slice(8)
          resultImageData.data.set([parseInt(binary, 2)], index)
          index++
        }
      }
      // imgData2dataUrl
      let canvasObj = document.createElement('canvas')
      canvasObj.width = width
      canvasObj.height = height
      let ctx = canvasObj.getContext('2d')
      ctx.putImageData(resultImageData, 0, 0)
      this.resultCanvas = canvasObj
      this.resultImageUrl = canvasObj.toDataURL('image/png')
      this.loadingCount--
    },
    // 解析隐藏文本
    parseHideText (imageData, threshold = 2) {
      let text = ''
      let tmpStr = ''
      let flag = false
      for (let i = 0; i < imageData.length; i += 4) {
        if (flag) break
        const r = imageData[i]
        const g = imageData[i + 1]
        const b = imageData[i + 2]
        const a = imageData[i + 3]
        const binStr = [num2binary(r), num2binary(g), num2binary(b), num2binary(a)].map(str => {
            return str.slice(8 - threshold, 8)
        }).join('')
        tmpStr += binStr
        const textByteCount = 16
        const numCount = Math.floor(tmpStr.length / textByteCount)
        for (let i = 0; i < numCount; i++) {
          const binary = tmpStr.slice(0, textByteCount)
          tmpStr = tmpStr.slice(textByteCount)
          let char = String.fromCharCode(parseInt(binary, 2))
          if (/^0101011000000010/.test(binary)) {
            flag = true
            break
          }
          text += char
        }
      }
      this.resultText = text
    },
    // 导出
    exportFile () {
      if (!this.resultCanvas) {
        return this.$toast('无数据')
      }
      this.loadingCount++
      let fileName = `parse_${Date.now()}.png`
      let filePath = path.join(this.saveFilePath, fileName)
      this.resultCanvas.toBlob((blob) => {
        this.loadingCount--
        if (!blob) {return this.$toast('无数据')}
        this.loadingCount++
        blob.arrayBuffer().then((res) => {
          this.loadingCount++
          this.$sharedObject('jstoolsNode').writeFile(filePath, Buffer.from(res)).then(() => {
            this.$toast('导出完成')
          }).catch((err) => {
            this.$toast('导出错误：' + err)
          }).finally(() => {
            this.loadingCount--
          })
        }).catch((err) => {
          this.$toast('发生错误：' + err)
        }).finally(() => {
          this.loadingCount--
        })
      }, 'image/png')
    },
  }
}
</script>

<style scoped lang="less">
@import "~@/assets/css/_mixins.less";
@import "~@/assets/css/_color.less";
.form-item-row {
  display: flex;
  .form-item-label {
    min-width: 4em;
    margin-right: .5em;
    text-align-last: justify;
  }
  .form-item-input {
    width: 0;
    flex-grow: 1;
    text-align: left;
  }
  .form-item-suffix {
    margin-left: .5em;
    min-width: 2em;
  }
  &[align="center"] {
    align-items: center;
  }
}
.cmp-parse {
  height: 100%;
  fieldset {
    height: 100%;
    .fieldset-content {
      height: calc(100% - 2em);
      display: flex;
      flex-direction: column;
      .form-group-main {
        height: 0;
        flex-grow: 1;
        img {
          display: block;
          width: 100%;
          height: 100%;
          object-fit: contain;
          background-color: @bgGrey;
        }
        textarea {
          display: block;
          width: 100%;
          height: 100%;
        }
      }
    }
  }
}
</style>
